﻿/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

using System;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;

namespace Hackerzhuli.Code.Editor
{
    internal class ProcessRunnerResult
    {
        public bool Success { get; set; }
        public string Output { get; set; }
        public string Error { get; set; }
    }

    internal static class ProcessRunner
    {
        public const int DefaultTimeoutInMilliseconds = 300000;

        public static ProcessStartInfo ProcessStartInfoFor(string filename, string arguments, bool redirect = true,
            bool shell = false)
        {
            return new ProcessStartInfo
            {
                UseShellExecute = shell,
                CreateNoWindow = true,
                RedirectStandardOutput = redirect,
                RedirectStandardError = redirect,
                FileName = filename,
                Arguments = arguments
            };
        }

        public static void Start(string filename, string arguments)
        {
            Start(ProcessStartInfoFor(filename, arguments, false));
        }

        public static void Start(ProcessStartInfo processStartInfo)
        {
            var process = new Process { StartInfo = processStartInfo };

            using (process)
            {
                process.Start();
            }
        }

        public static ProcessRunnerResult StartAndWaitForExit(string filename, string arguments,
            int timeoutms = DefaultTimeoutInMilliseconds, Action<string> onOutputReceived = null)
        {
            return StartAndWaitForExit(ProcessStartInfoFor(filename, arguments), timeoutms, onOutputReceived);
        }

        public static ProcessRunnerResult StartAndWaitForExit(ProcessStartInfo processStartInfo,
            int timeoutms = DefaultTimeoutInMilliseconds, Action<string> onOutputReceived = null)
        {
            var process = new Process { StartInfo = processStartInfo };

            using (process)
            {
                var sbOutput = new StringBuilder();
                var sbError = new StringBuilder();

                var outputSource = new TaskCompletionSource<bool>();
                var errorSource = new TaskCompletionSource<bool>();

                process.OutputDataReceived += (_, e) =>
                {
                    Append(sbOutput, e.Data, outputSource);
                    if (onOutputReceived != null && e.Data != null)
                        onOutputReceived(e.Data);
                };
                process.ErrorDataReceived += (_, e) => Append(sbError, e.Data, errorSource);

                process.Start();
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();

                var run = Task.Run(() => process.WaitForExit(timeoutms));
                var processTask = Task.WhenAll(run, outputSource.Task, errorSource.Task);

                if (Task.WhenAny(Task.Delay(timeoutms), processTask).Result == processTask && run.Result)
                    return new ProcessRunnerResult
                        { Success = true, Error = sbError.ToString(), Output = sbOutput.ToString() };

                try
                {
                    process.Kill();
                }
                catch
                {
                    /* ignore */
                }

                return new ProcessRunnerResult
                    { Success = false, Error = sbError.ToString(), Output = sbOutput.ToString() };
            }
        }

        private static void Append(StringBuilder sb, string data, TaskCompletionSource<bool> taskSource)
        {
            if (data == null)
            {
                taskSource.SetResult(true);
                return;
            }

            sb?.Append(data);
        }
    }
}